﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using SmartRoute.Analytics;
using SmartRoute.Randomness;

namespace SmartRoute.Optimization
{
    [Serializable]
    public class GeneticAlgorithm<TParameters>
    {
        protected GeneticAlgorithm() { }
        private bool _dirty = false;
        private GeneticAlgorithmGenome[] _population;
        private double[] _fitnesses;
        private int _championIndex;
        private uint _numberOfBuses;
        private IEnumerable<School> _schema;

        public uint NumberOfBuses
        {
            get { return _numberOfBuses; }
            set { 
                _numberOfBuses = value;
                _dirty = true;
                }
        }

        private void clean()
        {
            if (_dirty)
            {
                rebuildFitnessScores();
                _dirty = false;
            }
        }
        public uint PopulationSize
        {
            get { return (uint)_population.Length; }
            set {
                    _dirty = value > _population.Length;
                    if (_championIndex > value)
                    {
                        _population[0] = _population[_championIndex];
                        _fitnesses[0] = _fitnesses[_championIndex];
                        _championIndex = 0;
                    }
                    var tmpArray = new GeneticAlgorithmGenome[value];
                    var tmpFitnesses = new double[value];
                    int index = 0;
                    while (index < tmpArray.Length && index < _population.Length)
                        tmpArray[index] = _population[index++];
                    while (index < tmpArray.Length) tmpArray[index++] =
                        new GeneticAlgorithmGenome(_localRandomSource, _schema);
                    _population = tmpArray;
                    _fitnesses.CopyTo(tmpFitnesses, 0);
                    _fitnesses = tmpFitnesses;
                }
        }
        public ISolution<GeneticAlgorithmGenome> Champion
        {
            get { if(_dirty) rebuildFitnessScores(); return _population[_championIndex]; }
        }

        [NonSerialized]
        private IRoutingService _routeService = new ErrorRoutingService("Genetic Algorithm instance was not provided a routing service post deserialization.");

        public IRoutingService RouteService
        {
            get { return _routeService; }
            set { _routeService = value; }
        }

        private Location _depot;

        public Location Depot
        {
            get { return _depot; }
            set 
            { 
            _depot = value;
            _dirty = true;
            }
        }

        private RandomSource _localRandomSource;
        private Analyzer<TParameters> _fitnessTestToUse;
        private double _mutationRate;

        public double MutationRate
        {
            get { return _mutationRate; }
            set { _mutationRate = value; }
        }
        
        //Fitness function can only be changed if it uses the same parameters.
        public TParameters Parameters
        {
            get{ return _fitnessTestToUse.Parameters; }
            set { 
                _fitnessTestToUse.Parameters = value;
                _dirty = true;
                }
        }

        private void rebuildFitnessScores()
        {
            for (int i = 0; i < _population.Length; i++) updateFitness(i);
        }
        
        public GeneticAlgorithm(IRoutingService routeService, IEnumerable<School> schema,
            RandomSource randomSource, Analyzer<TParameters> fitnessTest, uint populationSize, double mutationRate, uint numberOfBuses
            , Location depot)
        {
            this._routeService = routeService;
            this._localRandomSource = randomSource;
            this._mutationRate = mutationRate;
            this._numberOfBuses = numberOfBuses;
            this._fitnessTestToUse = fitnessTest;
            this._championIndex = 0;
            this._population = new GeneticAlgorithmGenome[populationSize];
            this._fitnesses = new double[populationSize];
            this._depot = depot;
            this._schema = schema;
            for (int i = 0; i < populationSize; i++) _population[i] = new GeneticAlgorithmGenome(randomSource, _schema);
            rebuildFitnessScores();
        }

        private void updateFitness(int index)
        {
            _fitnesses[index] = 
                _fitnessTestToUse.runTest(
                ISolutionFunctions.SolveForNBuses(_routeService,_depot,_population[index],_numberOfBuses).SelectMany(x => x)
            );
            if(_fitnesses[index] > _fitnesses[_championIndex]) _championIndex = index;
        }

        private void overwriteLoser(int winner1, int winner2, int loser)
        {
            _population[loser] = GeneticAlgorithmGenome.Crossover(_localRandomSource,_population[winner1],_population[winner2]);
            if(_localRandomSource.NextDouble() > _mutationRate) _population[loser] = _population[loser].Mutate(_localRandomSource);
            updateFitness(loser);
        }

        public void StepN(int steps)
        {
            clean();
            for(int i = 0; i < steps; i++)
            {
                int ix1 = _localRandomSource.Next(0,_population.Length);
                int ix2 = _localRandomSource.Next(0,_population.Length);
                int ix3 = _localRandomSource.Next(0,_population.Length);
                if(_fitnesses[ix1] > _fitnesses[ix2])
                {
                    if(_fitnesses[ix2] > _fitnesses[ix3]) overwriteLoser(ix1,ix2,ix3);
                    else overwriteLoser(ix1,ix3,ix2);
                }
                else if(_fitnesses[ix1] > _fitnesses[ix3]) overwriteLoser(ix1,ix2,ix3);
                else overwriteLoser(ix2,ix3,ix1);
            }
        }

        public void Step()
        {
            StepN(1);
        }

        public void TraditionalStep()
        {
            clean();
            StepN(_population.Length);
        }
    }
}
